package com.dtflys.easyel.ast;

import com.dtflys.easyel.antlr.EasyElParser;
import com.dtflys.easyel.antlr.EasyElParserBaseVisitor;
import com.dtflys.easyel.compile.EasyElCompileConfiguration;
import com.dtflys.easyel.compile.EasyElErrorListener;
import com.dtflys.easyel.compile.EasyElSource;
import com.dtflys.easyel.transform.AccessExpressionTransformer;
import com.dtflys.easyel.transform.BinaryExpressionTransformer;
import com.dtflys.easyel.transform.ConstantTransformer;
import com.dtflys.easyel.transform.DateTransformer;
import com.dtflys.easyel.transform.IdentifierTransformer;
import com.dtflys.easyel.transform.IndexExpressionTransformer;
import com.dtflys.easyel.transform.InvokeExpressionTransformer;
import com.dtflys.easyel.transform.ListGeneratorTransformer;
import com.dtflys.easyel.transform.ListTransformer;
import com.dtflys.easyel.transform.MapEntryTransformer;
import com.dtflys.easyel.transform.MapTransformer;
import com.dtflys.easyel.transform.NegativeExpressionTransformer;
import com.dtflys.easyel.transform.NewInstanceTransformer;
import com.dtflys.easyel.transform.NotExpressionTransformer;
import com.dtflys.easyel.transform.NullTransformer;
import com.dtflys.easyel.transform.RangeTransformer;
import com.dtflys.easyel.transform.TernaryExpressionTransformer;
import com.dtflys.easyel.transform.TimeDurationTransformer;
import com.dtflys.easyel.transform.TypeTransformer;
import com.dtflys.easyel.utils.NumberHelper;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.apache.commons.collections4.CollectionUtils;


import java.math.BigDecimal;
import java.util.*;

/**
 * @author gongjun[jun.gong@thebeastshop.com]
 * @since v1.0.0
 */
public class ASTTransformVisitor extends EasyElParserBaseVisitor<ASTNode> {

    private final EasyElSource source;
    private final EasyElCompileConfiguration compileConfiguration;
    private final EasyElErrorListener errorListener;

    private final static NullTransformer nullTransformer = new NullTransformer();
    private final static ConstantTransformer constantTransformer = new ConstantTransformer();
    private final static RangeTransformer rangeTransformer = new RangeTransformer();
    private final static DateTransformer dateTransformer = new DateTransformer();
    private final static TimeDurationTransformer timeDurationTransformer = new TimeDurationTransformer();
    private final static BinaryExpressionTransformer binaryExpressionTransformer = new BinaryExpressionTransformer();
    private final static TernaryExpressionTransformer ternaryExpressionTransformer = new TernaryExpressionTransformer();
    private final static NegativeExpressionTransformer negativeExpressionTransformer = new NegativeExpressionTransformer();
    private final static NotExpressionTransformer notExpressionTransformer = new NotExpressionTransformer();
    private final static AccessExpressionTransformer accessExpressionTransformer = new AccessExpressionTransformer();
    private final static IndexExpressionTransformer indexExpressionTransformer = new IndexExpressionTransformer();
    private final static InvokeExpressionTransformer invokeExpressionTransformer = new InvokeExpressionTransformer();
    private final static NewInstanceTransformer newInstanceTransformer = new NewInstanceTransformer();
    private final static IdentifierTransformer identifierTransformer = new IdentifierTransformer();
    private final static TypeTransformer typeTransformer = new TypeTransformer();
    private final static ListTransformer listTransformer = new ListTransformer();
    private final static ListGeneratorTransformer listGeneratorTransformer = new ListGeneratorTransformer();
    private final static MapEntryTransformer mapEntryTransformer = new MapEntryTransformer();
    private final static MapTransformer mapTransformer = new MapTransformer();

    public ASTTransformVisitor(EasyElSource source, EasyElCompileConfiguration compileConfiguration, EasyElErrorListener errorListener) {
        this.source = source;
        this.compileConfiguration = compileConfiguration;
        this.errorListener = errorListener;
    }


    @Override
    public ASTNode visitPathExpression(EasyElParser.PathExpressionContext ctx) {
        ASTExpression firstExpr = (ASTExpression) visitPrimary(ctx.primary());
        List<EasyElParser.PathElementContext> pathElementList = ctx.pathElement();
        ASTExpression leftExpr = firstExpr;
        boolean isClassName = firstExpr instanceof ASTIdentifier;
        LinkedList<String> classNameQueue = new LinkedList<>();
        Class clazz = null;
        boolean maybeInner = false;
        for (EasyElParser.PathElementContext pathElement : pathElementList) {
            ASTExpression right = visitPathElement(pathElement);
            Class cls = null;

            if (right instanceof ASTAccessExpression) {
                ASTAccessExpression accessExpression = (ASTAccessExpression) right;

                if (leftExpr instanceof ASTAccessExpression && maybeInner) {
                    cls = findClass(clazz.getName() + "$" + ((ASTAccessExpression) leftExpr).getAccessName().getText());
                    if (cls != null) {
                        clazz = cls;
                        ASTType type = new ASTType(clazz.getSimpleName(), clazz);
                        accessExpression.setLeft(type);
                        accessExpression.setStatic(true);
                    }
                }

                if (isClassName) {
                    if (classNameQueue != null && !classNameQueue.isEmpty()) {
                        String className = getClassNameFromList(firstExpr.getText(), classNameQueue);
                        cls = findClass(className);
                        if (cls != null) {
                            if (clazz == null) {
                                clazz = cls;
                                ASTType type = new ASTType(clazz.getSimpleName(), clazz);
                                accessExpression.setLeft(type);
                                accessExpression.setStatic(true);
                                maybeInner = true;
                            }
                            classNameQueue = null;
                        }
                    }
                    if (classNameQueue != null) {
                        classNameQueue.add(accessExpression.getAccessName().getText());
                    }
                }
                if (cls == null) {
                    accessExpression.setLeft(leftExpr);
                }
                leftExpr = right;
                continue;
            } else if (right instanceof ASTInvokeExpression) {
                if (isClassName) {
                    isClassName = false;
                    if (classNameQueue != null && !classNameQueue.isEmpty()) {
                        classNameQueue.pollLast();
                        String className = getClassNameFromList(firstExpr.getText(), classNameQueue);
                        classNameQueue = null;
                        clazz = findClass(className);
                        if (clazz != null) {
                            ASTType type = new ASTType(clazz.getSimpleName(), clazz);
                            ((ASTAccessExpression) leftExpr).setLeft(type);
                        }
                    }
                }
                ASTInvokeExpression invokeExpression = (ASTInvokeExpression) right;
                if (leftExpr instanceof ASTAccessExpression) {
                    ASTAccessExpression accessExpression = (ASTAccessExpression) leftExpr;
                    ASTExpression accessLeft = accessExpression.getLeft();
                    if (accessLeft instanceof ASTType) {
                        invokeExpression.setStatic(true);
                        invokeExpression.setStaticType((ASTType) accessLeft);
                    } else {
                        invokeExpression.setLeft(accessLeft);
                    }
                    invokeExpression.setMethodName(accessExpression.getAccessName());
                    invokeExpression.setStartLineNumber(leftExpr.getStartLineNumber());
                    invokeExpression.setStartColumnNumber(leftExpr.getStartColumnNumber());
                    leftExpr = invokeExpression;
                    continue;
                }
            } else if (right instanceof ASTIndexExpression) {
                ASTIndexExpression indexExpression = (ASTIndexExpression) right;
                indexExpression.setLeft(leftExpr);
                leftExpr = indexExpression;
                continue;
            }
        }
        return leftExpr;
    }


    private List<ASTExpression> getArgumentList(EasyElParser.ArgumentsContext argumentsContext) {
        EasyElParser.ArgumentListContext argumentListContext = argumentsContext.argumentList();
        List<ASTExpression> argumentExprs = new ArrayList<>();
        if (argumentListContext != null) {
            List<EasyElParser.ArgumentListElementContext> listElementContexts =
                    argumentListContext.argumentListElement();
            for (EasyElParser.ArgumentListElementContext elementContext : listElementContexts) {
                ASTExpression argExpr = visitArgumentListElement(elementContext);
                argumentExprs.add(argExpr);
            }
        }
        return argumentExprs;
    }

    @Override
    public ASTExpression visitPathElement(EasyElParser.PathElementContext ctx) {
        switch (ctx.t) {
            case 1:
                EasyElParser.NamePartContext namePart = ctx.namePart();
                ASTConstant name = constantTransformer.transformName(namePart.stop);
                return accessExpressionTransformer.transform(namePart.start, name);
            case 2:
                EasyElParser.ArgumentsContext argumentsContext = ctx.arguments();
                List<ASTExpression> argumentExprs = getArgumentList(argumentsContext);
                return invokeExpressionTransformer.transform(argumentExprs, ctx.start, ctx.stop);
            case 3:
                ASTExpression indexArgs = visitIndexPropertyArgs(ctx.indexPropertyArgs());
                return indexExpressionTransformer.transform(indexArgs, ctx.start, ctx.stop);
        }
        return null;
    }


    @Override
    public ASTExpression visitArgumentListElement(EasyElParser.ArgumentListElementContext ctx) {
        return visitExpression(ctx.expression());
    }

    @Override
    public ASTExpression visitExpressionInParen(EasyElParser.ExpressionInParenContext ctx) {
        return visitExpression(ctx.expression());
    }

    @Override
    public ASTExpression visitIndexPropertyArgs(EasyElParser.IndexPropertyArgsContext ctx) {
        return visitExpression(ctx.expression());
    }

    @Override
    public ASTExpression visitExpression(EasyElParser.ExpressionContext ctx) {
        Token op = ctx.op;
        EasyElParser.ExpressionContext left = ctx.left;
        EasyElParser.ExpressionContext right = ctx.right;
        EasyElParser.ExpressionContext con = ctx.con;
        if (con != null) {
            ASTExpression condExpr = visitExpression(con);
            ASTExpression trueExpr = null;
            if (ctx.tb != null) {
                trueExpr = visitExpression(ctx.tb);
            }
            ASTExpression falseExpr = null;
            if (ctx.tf != null) {
                falseExpr = visitExpression(ctx.tf);
            }
            return ternaryExpressionTransformer.transform(condExpr, trueExpr, falseExpr);
        }
        if (op != null) {
            if (left != null && right != null) {
                ASTExpression leftExpr = visitExpression(left);
                ASTExpression rightExpr = visitExpression(right);
                return binaryExpressionTransformer.transform(leftExpr, op, ctx.not, rightExpr);
            } else {
                ASTExpression expr = visitExpression(ctx.expression(0));
                switch (op.getType()) {
                    case EasyElParser.SUB:
                        return negativeExpressionTransformer.transform(expr, ctx.start, ctx.stop);
                    case EasyElParser.ADD:
                        expr.setStartLineNumber(ctx.start.getLine());
                        expr.setStartColumnNumber(ctx.start.getStartIndex());
                        return expr;
                    case EasyElParser.NOT:
                    case EasyElParser.J_NOT:
                        return notExpressionTransformer.transform(expr, ctx.start, ctx.stop);
                }
            }
        }
        return (ASTExpression) visitChildren(ctx);
    }


    @Override
    public ASTList visitList(EasyElParser.ListContext ctx) {
        EasyElParser.ListItemListContext listItemListContext = ctx.listItemList();
        if (listItemListContext == null) {
            return listTransformer.transform(null, ctx.start, ctx.stop);
        }
        List<EasyElParser.ListItemContext> items = listItemListContext.listItem();
        List<ASTExpression> itemExprs = new ArrayList<>();
        for (EasyElParser.ListItemContext item : items) {
            ASTExpression itemExpr = visitListItem(item);
            itemExprs.add(itemExpr);
        }
        return listTransformer.transform(itemExprs, ctx.start, ctx.stop);
    }


    @Override
    public ASTNode visitListGenerator(EasyElParser.ListGeneratorContext ctx) {
        ASTExpression itemExpr = visitExpression(ctx.item);
        List<EasyElParser.IdentifierContext> nameListContext = ctx.con.nameList.identifier();
        List<ASTIdentifier> nameList = new ArrayList<>();
        for (EasyElParser.IdentifierContext nameContext : nameListContext) {
            nameList.add(identifierTransformer.transform(nameContext.start));
        }
        ASTExpression forConditionRight = visitExpression(ctx.con.right);
        ASTListGenerator listGenerator = listGeneratorTransformer.transform(
                itemExpr, nameList, forConditionRight, ctx.start, ctx.stop);
        return listGenerator;
    }

    @Override
    public ASTExpression visitListItem(EasyElParser.ListItemContext ctx) {
        return visitExpression(ctx.expression());
    }

    @Override
    public ASTMap visitMap(EasyElParser.MapContext ctx) {
        Token start = ctx.getStart();
        Token end = ctx.getStop();
        EasyElParser.MapEntryListContext mapEntryListContext = ctx.mapEntryList();
        if (mapEntryListContext == null) {
            mapTransformer.transform(null, start, end);
        }
        List<EasyElParser.MapEntryContext> entryContextList = mapEntryListContext.mapEntry();
        List<ASTMapEntry> entries = new ArrayList<>();
        for (EasyElParser.MapEntryContext entryContext : entryContextList) {
            ASTMapEntry entry = visitMapEntry(entryContext);
            entries.add(entry);
        }
        return mapTransformer.transform(entries, start, end);
    }


    @Override
    public ASTMapEntry visitMapEntry(EasyElParser.MapEntryContext ctx) {
        EasyElParser.MapEntryLabelContext labelContext = ctx.mapEntryLabel();
        ASTExpression labelExpr = (ASTExpression) visitMapEntryLabel(labelContext);
        ASTExpression valueExpr = visitExpression(ctx.expression());
        return mapEntryTransformer.transform(labelExpr, valueExpr, ctx.getStart(), ctx.getStop());
    }

    @Override
    public ASTNode visitMapEntryLabel(EasyElParser.MapEntryLabelContext ctx) {
        return super.visitMapEntryLabel(ctx);
    }

    @Override
    public ASTNode visitNamePart(EasyElParser.NamePartContext ctx) {
        Token token = ctx.start;
        ASTType type = typeTransformer.transform(token, compileConfiguration);
        if (type != null) {
            return type;
        }
        return identifierTransformer.transform(token);
    }

    @Override
    public ASTNode visitNullLiteral(EasyElParser.NullLiteralContext ctx) {
        return nullTransformer.transform(ctx.getStart());
    }


    @Override
    public ASTNode visitNewInstance(EasyElParser.NewInstanceContext ctx) {
        ASTNewInstance node = visitConstructor(ctx.constructor());
        if (ctx.props != null) {
            ASTMap propsNode = visitMap(ctx.props);
            node.setProperties(propsNode);
            node.setEndLineNumber(propsNode.getEndLineNumber());
            node.setEndColumnNumber(propsNode.getEndColumnNumber());
        }
        return node;
    }


    @Override
    public ASTNewInstance visitConstructor(EasyElParser.ConstructorContext ctx) {
        ASTType type = (ASTType) visit(ctx.constructorName());

        EasyElParser.ArgumentsContext argumentsContext = ctx.arguments();
        List<ASTExpression> argumentExprs = getArgumentList(argumentsContext);

        return newInstanceTransformer.transform(type, argumentExprs, ctx.start, ctx.stop);
    }

    @Override
    public ASTType visitConstructorName(EasyElParser.ConstructorNameContext ctx) {
        ASTType type = visitQualifiedClassName(ctx.qualifiedClassName());
        return type;
    }

    @Override
    public ASTType visitQualifiedClassName(EasyElParser.QualifiedClassNameContext ctx) {
        StringBuilder builder = new StringBuilder();
        EasyElParser.ClassNameContext classNameContext = ctx.className();
        EasyElParser.QualifiedNameElementsContext qualifiedNameElementsContext = ctx.qualifiedNameElements();
        List<EasyElParser.IdentifierContext> identifierContexts = qualifiedNameElementsContext.identifier();
        boolean bInnerClass = false;
        if (!CollectionUtils.isEmpty(identifierContexts)) {
            for (int i = 0; i < identifierContexts.size(); i++) {
                EasyElParser.IdentifierContext identifierContext = identifierContexts.get(i);
                String name = identifierContext.start.getText();
                builder.append(name);
                Class cls = findClass(builder.toString());
                if (cls != null) {
                    builder = new StringBuilder(cls.getName());
                    bInnerClass = true;
                }
                if (bInnerClass) {
                    builder.append('$');
                } else {
                    builder.append('.');
                }
            }
        }
        String simpleClassName = classNameContext.getText();
        builder.append(simpleClassName);
        String className = builder.toString();

        Class clazz = findClass(className);
        if (clazz == null) {
            errorListener.addError(ctx, "Can not resolve class name \"" + className + "\"");
            return null;
        }
        return new ASTType(simpleClassName, clazz);
    }

    private String getClassNameFromList(String head, LinkedList<String> tail) {
        StringBuilder builder = new StringBuilder(head);
        for (String cname : tail) {
            if (cname.charAt(0) != '$') {
                builder.append('.');
            }
            builder.append(cname);
        }
        return builder.toString();
    }



    private Class findClass(String className) {
        Class clazz = compileConfiguration.getImportedClasses().get(className);
        if (clazz == null) {
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
            }
        }
        return clazz;
    }

    @Override
    public ASTConstant visitIntLiteral(EasyElParser.IntLiteralContext ctx) {
        int type = ctx.t;
        String text = ctx.getText();
        switch (type) {
            case 0:
                Object value = NumberHelper.parseIntegerNumber(text);
                return constantTransformer.transform(source, value, ctx.getStart(), ctx.getStop());
            case 1:
                Long longValue = Long.parseLong(text.substring(0, text.length() - 1));
                return constantTransformer.transform(source, longValue, ctx.getStart(), ctx.getStop());
        }
        return null;
    }

    @Override
    public ASTConstant visitNumericLiteral(EasyElParser.NumericLiteralContext ctx) {
        int type = ctx.t;
        String text = ctx.getText();
        switch (type) {
            case 0:
                BigDecimal decimal = new BigDecimal(text);
                return constantTransformer.transform(source, decimal, ctx.getStart(), ctx.getStop());
            case 1:
                Float floatValue = Float.parseFloat(text.substring(0, text.length() - 1));
                return constantTransformer.transform(source, floatValue, ctx.getStart(), ctx.getStop());
            case 2:
                Double doubleValue = Double.parseDouble(text.substring(0, text.length() - 1));
                return constantTransformer.transform(source, doubleValue, ctx.getStart(), ctx.getStop());
            case 3:
                BigDecimal bigDecimal = new BigDecimal(text.substring(0, text.length() - 1));
                return constantTransformer.transform(source, bigDecimal, ctx.getStart(), ctx.getStop());
        }
        return null;
    }

    @Override
    public ASTConstant visitStringLiteral(EasyElParser.StringLiteralContext ctx) {
        String text = ctx.getText();
        String value = text.substring(1, text.length() - 1);
        return constantTransformer.transform(source, value, ctx.start, ctx.stop);
    }

    @Override
    public ASTRange visitRangeLiteral(EasyElParser.RangeLiteralContext ctx) {
        ASTExpression leftExpr = ctx.left == null ? null : visitExpression(ctx.left);
        ASTExpression rightExpr = ctx.right == null ? null : visitExpression(ctx.right);
        return rangeTransformer.transform(leftExpr, rightExpr, ctx.start, ctx.stop);
    }

    @Override
    public ASTNode visitDateLiteral(EasyElParser.DateLiteralContext ctx) {
        return dateTransformer.transform(ctx.yyyyMMdd, ctx.time, ctx.start, ctx.stop);
    }

    @Override
    public ASTNode visitDurationLiteral(EasyElParser.DurationLiteralContext ctx) {
        return timeDurationTransformer.transform(ctx.start);
    }

    @Override
    public ASTNode visitBooleanLiteral(EasyElParser.BooleanLiteralContext ctx) {
        String text = ctx.getText();
        Boolean value = "true".equals(text);
        return constantTransformer.transform(source, value, ctx.start, ctx.stop);
    }


    @Override
    public ASTIdentifier visitKeywords(EasyElParser.KeywordsContext ctx) {
        return identifierTransformer.transform(ctx.start);
    }

    @Override
    public ASTNode visitErrorNode(ErrorNode node) {
        return super.visitErrorNode(node);
    }
}
